/*
 * Copyright 2009 Max Kugland
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.splink.deepsplink.page.request {
	import org.splink.deepsplink.config.ConfigData;
	import org.splink.deepsplink.config.OverlayData;
	import org.splink.deepsplink.config.PageData;
	import org.splink.deepsplink.page.IPageInternal;
	import org.splink.deepsplink.page.PageFactory;
	import org.splink.deepsplink.page.operation.IPageOperation;
	import org.splink.deepsplink.page.operation.PageFinalizeOperation;
	import org.splink.library.logging.ILogger;
	import org.splink.library.logging.LogLevel;
	import org.splink.library.logging.LoggerProvider;
	import org.splink.library.queue.IQ;
	import org.splink.library.queue.Q;
	import org.splink.library.queue.QEvent;
	import org.splink.library.tree.INode;
	import org.splink.library.tree.Tree;
	import org.splink.library.utils.ArrayUtils;

	import flash.display.Sprite;

	/**
	 * The <code>RequestProcessor</code> inspects the incoming
	 * gotoPage request and delegates the task to construct
	 * the resulting asyncronous operations to an 
	 * <code>IRequestBuilder</code> implementation which returns
	 * an array of <code>IQable</code> instances. These are
	 * added to the <code>RequestProcessor</code> queue which
	 * is started for each request. If a request hasn't completed
	 * when a new request arrives, the currently running request
	 * is stopped and the new request executed with the current
	 * page as starting point for the new request.
	 * 
	 * @author Max Kugland
	 */
	final public class RequestProcessor implements IRequestProcessor {
		private static const _logger:ILogger = LoggerProvider.getLogger("deepsplink", RequestProcessor);
		private var _display : Sprite;
		private var _overlayDisplay : Sprite;
		private var _tree : Tree;
		private var _queue : IQ;
		private var _overlayIds : Array;
		private var _factory : RequestBuilderFactory;
		private var _config : ConfigData;
		private var _first : Boolean = true;
		private var _pageFactory : PageFactory;

		/**
		 * @param pageFactory a <code>PageFactory</code> which is used to instantiate lazy pages on demand
		 * @param tree deepsplinks underlying <code>Tree</code> which defines the page structure and contains 
		 * <code>IPageInternal</code> instances in it's data property
		 * @param display the <code>DisplayObjectContainer</code> which will server as container for the pages
		 * @param overlayIds an <code>Array</code> of Strings defining the pages to be handled as overlays
		 */
		public function RequestProcessor(tree : Tree, display : Sprite, config : ConfigData, pageFactory : PageFactory) {
			_pageFactory = pageFactory;
			_tree = tree;
			_config = config;
			_overlayIds = getOverlayPageIds(_config);
			if(_config.overlayData && ArrayUtils.contains(_overlayIds, _config.overlayData.defaultBackingPage)) {
				throw new Error("The defaultBackingPage for overlays must not be an overlay.");
			}

			display.addChild(_display = new Sprite());
			display.addChild(_overlayDisplay = new Sprite());

			_factory = new RequestBuilderFactory(config, _tree, _overlayIds);
			_queue = new Q();
		}

		/**
		 * @inheritDoc
		 */
		public function gotoPage(id : String, pageParams : Array) : void {
			_logger.log(LogLevel.INFO, "gotoPage: " + id + " params: " + pageParams);
			var node : INode = Tree.getNodeById(id, _tree.root);
			if(node == null) {
				return;
			}

			_pageFactory.createLazyPage(node.id);

			var operations : Array;
			if(isOverlay(id)) {
				operations = getOverlayOperations(id, pageParams);
				// if the first requested page ever is an overlay page, also show the defaultBackingPage if there is one
				if(_first && _config.overlayData && _config.overlayData.defaultBackingPage) {
					operations = getPageOperations(_config.overlayData.defaultBackingPage, pageParams).
					concat(getOverlayOperations(id, pageParams));
				}
			} else {
				operations = getPageOperations(id, pageParams);
			}

			addToQueue(operations).start();
			_first = false;
		}

		private function getPageOperations(id : String, pageParams : Array) : Array {
			var doOpen : Array = getNodesToOpen(Tree.getNodeById(id, _tree.root), _tree.root);
			var all : Array = getAllNodes(_tree.root);
			return _factory.create(id).build(_display, doOpen, all, pageParams);
		}

		private function getOverlayOperations(id : String, pageParams : Array) : Array {
			var doOpen : Array = getNodesToOpen(Tree.getNodeById(id, _tree.root), _tree.root);
			var all : Array = getOverlayNodes(Tree.getAllChildren(_tree.root));
			return _factory.create(id).build(_overlayDisplay, doOpen, all, pageParams);
		}

		private function addToQueue(operations : Array) : IQ {
			_queue.finalize();
			
			for each (var op : IPageOperation in operations) {
				_queue.add(op);
			}
			_queue.register(QEvent.COMPLETE, function(e : QEvent) : void {
				_queue.finalize();
			});

			return _queue;
		}

		/**
		 * @return an <code>Array</code> of <code>INode</code>
		 */
		private function getOverlayNodes(nodes : Array) : Array {
			var ar : Array = new Array();
			for each (var node : INode in nodes) {
				if(isOverlay(node.id)) {
					ar.push(node);
				}
			}
			return ar;
		}

		private function isOverlay(id : String) : Boolean {
			return ArrayUtils.contains(_overlayIds, id);
		}

		private static function getAllNodes(root : INode) : Array {
			var all : Array = Tree.getAllChildren(root);
			all.unshift(root);
			return all;
		}

		private static function getNodesToOpen(clickedNode : INode, root : INode) : Array {
			var doOpen : Array = Tree.getParentNodeChain(clickedNode);
			doOpen.push(clickedNode);
			return doOpen;
		}

		/**
		 * @return an <code>Array</code> of <code>String</code> ids each of them a reference 
		 * to an overlay page
		 */
		private static function getOverlayPageIds(config : ConfigData) : Array {
			var ar : Array = new Array();
			var overlayData : OverlayData = config.overlayData;
			if(overlayData) {
				for each (var pageData : PageData in overlayData.pageDataArray) {
					ar.push(pageData.id);
				}
			}
			return ar;
		}

		/**
		 * @inheritDoc
		 */
		public function finalize() : void {
			_queue.finalize();

			var all : Array = getAllNodes(_tree.root);
			for each (var node : INode in all) {
				new PageFinalizeOperation(IPageInternal(node.data)).start();
			}
		}
	}
}